package com.zhiqian.community.service;

import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.zhiqian.community.dao.DiscussPostMapper;
import com.zhiqian.community.entity.*;
import com.zhiqian.community.event.EventProducer;
import com.zhiqian.community.util.CommunityConstant;
import com.zhiqian.community.util.MarkdownUtil;
import com.zhiqian.community.util.RedisKeyUtil;
import com.zhiqian.community.util.SensitiveFilter;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.util.HtmlUtils;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

@Service
public class DiscussPostService implements CommunityConstant {
    private static final Logger logger= LoggerFactory.getLogger(DiscussPostService.class);
    //预编译正则表达式
    private static final Pattern pattern1=Pattern.compile("\\&[a-zA-Z]{1,10};");
    private static final Pattern pattern2=Pattern.compile("<[^>]*>");
    private static final Pattern pattern3=Pattern.compile("[(/>)<]");
    @Autowired
    private DiscussPostMapper discussPostMapper;
    @Autowired
    private UserService userService;
    @Autowired
    private SensitiveFilter sensitiveFilter;
    @Autowired
    private LikeService likeService;
    @Autowired
    private EventProducer eventProducer;
    @Autowired
    private ElasticsearchService elasticsearchService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Value("${caffeine.posts.max-size}")
    private int caffeineMaxSize;
    @Value("${caffeine.posts.rows.max-size}")
    private int caffeineRowsMaxSize;
    @Value("${caffeine.posts.expire-seconds}")
    private int caffeineExpireSeconds;

    //Caffeine 核心接口：Cache  常用子接口：LoadingCache、AsyncLoadingCache
    //帖子列表缓存
    private LoadingCache<String,List<DiscussPost>> postListCache;
    //帖子总数缓存
    private LoadingCache<Integer,Integer> postRowsCache;

    //缓存初始化
    @PostConstruct
    public void init(){
        //初始化帖子列表缓存
        postListCache = Caffeine.newBuilder()
                .maximumSize(caffeineMaxSize)
                .expireAfterWrite(caffeineExpireSeconds, TimeUnit.SECONDS)
                .build(new CacheLoader<String, List<DiscussPost>>() {//这一步设置，当Caffeine缓存中没有数据时，去哪里查询数据
                    @Nullable
                    @Override
                    public List<DiscussPost> load(String key) throws Exception {
                        if (StringUtils.isBlank(key)){
                            throw new IllegalArgumentException("参数错误！");
                        }
                        String[] params = key.split(":");
                        if (params ==null||params.length!=2){
                            throw new IllegalArgumentException("参数错误！");
                        }
                        int offset=Integer.valueOf(params[0]);
                        int limit=Integer.valueOf(params[1]);
                        logger.debug("load post list from DB.");
                        return discussPostMapper.selectDiscussPosts(null,offset,limit,ORDER_MODE_HOT);
                    }
                });
        //初始化帖子总数缓存
        postRowsCache = Caffeine.newBuilder()
                .maximumSize(caffeineRowsMaxSize)
                .expireAfterWrite(caffeineExpireSeconds,TimeUnit.SECONDS)
                .build(new CacheLoader<Integer, Integer>() {
                    @Nullable
                    @Override
                    public Integer load(Integer integer) throws Exception {
                        logger.debug("load post rows from DB.");
                        return discussPostMapper.selectDiscussPostRows(null);
                    }
                });
    }

    /**
     * 查询帖子列表
     * @param userId
     * @param page
     * @param orderMode
     * @return
     */
    public List<Map<String,Object>> findDiscussPosts(Integer userId, Page page,int orderMode){
        List<DiscussPost> discussPosts;
        if (userId == null && orderMode == ORDER_MODE_HOT){
            discussPosts = postListCache.get(page.getOffset() + ":" + page.getLimit());
        }else {
            discussPosts = discussPostMapper.selectDiscussPosts(userId, page.getOffset(), page.getLimit(), orderMode);
            logger.debug("load post list from DB.");
        }
        return getUserInfoByDiscussPosts(discussPosts,userId==null);
    }

    private List<Map<String,Object>> getUserInfoByDiscussPosts(List<DiscussPost> discussPosts,boolean isFindUser){
        List<Map<String,Object>> list=new ArrayList<>();
        discussPosts.stream().forEach(discussPost -> {
            Map<String, Object> map = new HashMap<>(3);
            //将markdown文本处理成普通文本
            discussPost.setContent(MarkdownUtil.markdownToHtmlExtensions(discussPost.getContent()).replaceAll(pattern1.pattern(), "").replaceAll(
                    pattern2.pattern(), "").replaceAll(pattern3.pattern(), ""));
            map.put("post", discussPost);
            if (isFindUser) {
                map.put("user", userService.getUserById(discussPost.getUserId()));
            }
            map.put("likeCount",likeService.queryEntityLikeCount(ENTITY_TYPE_POST,discussPost.getId()));
            list.add(map);
        });
        return list;
    }

    /**
     * 查询满足某些条件的帖子总数
     * @param userId
     * @return
     */
    public int findDiscussPostRows(Integer userId){
        if (userId == null){
            return postRowsCache.get(1);
        }
        logger.debug("load post rows from DB.");
        return discussPostMapper.selectDiscussPostRows(userId);
    }

    /**
     * 添加文章或修改文章
     * @param discussPost
     * @return
     */
    public int insertDiscussPost(DiscussPost discussPost,boolean isUpdate){
        if (discussPost==null){
            throw new IllegalArgumentException("参数不能为空");
        }
        //转义HTML标记,Spring自带转义工具类：HtmlUtils
        discussPost.setTitle(sensitiveFilter.filter(HtmlUtils.htmlEscape(discussPost.getTitle())));
        discussPost.setContent(sensitiveFilter.filter(discussPost.getContent()));
        if (discussPost.getDraft().equals(DISCUSSPOST_DRAFT)){
            discussPost.setDraft(0);
        }
        int rows;
        if (isUpdate) {
            rows = discussPostMapper.update(discussPost);
        }else {
            discussPost.setCreateTime(new Date());
            rows = discussPostMapper.insertDiscussPost(discussPost);
            redisTemplate.opsForSet().add(RedisKeyUtil.getVariationPostKey(), discussPost.getId());
            Event informEvent = getInformEvent(discussPost);
            eventProducer.fireEvent(informEvent);
        }
        Event event=getCreatePostEvent(discussPost);
        eventProducer.fireEvent(event);
        return rows;
    }

    private Event getInformEvent(DiscussPost discussPost){
        Event event = new Event();
        event.setTopic(TOPIC_INFORM);
        event.setEntityType(ENTITY_TYPE_POST);
        event.setEntityId(discussPost.getId());
        event.setSourceId(discussPost.getUserId());
        return event;
    }

    private Event getCreatePostEvent(DiscussPost discussPost) {
        Event event = new Event();
        event.setTopic(TOPIC_PUBLISH);
        event.setEntityId(discussPost.getId());
        event.setSourceId(discussPost.getUserId());
        event.setEntityType(ENTITY_TYPE_POST);
        return event;
    }

    public int incrementCommentCount(Integer entityId){
        return discussPostMapper.incrementCommentCount(entityId);
    }

    /**
     * 根据文章id查询文章信息
     * @param id
     * @return
     */
    public Map<String,Object> selectDiscussPost(Integer id){
        DiscussPost post = discussPostMapper.selectDiscussPostById(id);
        Map<String, Object> map = new HashMap<>();
        if (post != null) {
            post.setContent(MarkdownUtil.markdownToHtmlExtensions(post.getContent()));
            User user = userService.getUserById(post.getUserId());
            map.put("post", post);
            map.put("user", user);
            map.put("likeCount", likeService.queryEntityLikeCount(ENTITY_TYPE_POST, post.getId()));
        }
        return map;
    }

    public DiscussPost selectDiscussPostById(Integer id){
        return discussPostMapper.selectDiscussPostById(id);
    }

    /**
     * 置顶或取消置顶
     * @param discussPostId
     */
    public int stickOrUnstick(Integer discussPostId,Integer userId){
        DiscussPost post = discussPostMapper.selectDiscussPostById(discussPostId);
        if (post == null){
            return HANDLE_FAIL;
        }
        Integer postType = post.getType();
        int row=postType.equals(DISCUSSPOST_TYPE_COMMON)?
                discussPostMapper.updateDiscussPostType(discussPostId,DISCUSSPOST_TYPE_STICK):
                discussPostMapper.updateDiscussPostType(discussPostId,DISCUSSPOST_TYPE_COMMON);
        Event event = getUpdatePostEvent(post.getId(),userId);
        eventProducer.fireEvent(event);
        return row;
    }

    private Event getUpdatePostEvent(Integer postId, Integer userId) {
        Event event = new Event();
        event.setTopic(TOPIC_PUBLISH);
        event.setSourceId(userId);
        event.setEntityType(ENTITY_TYPE_POST);
        event.setEntityId(postId);
        return event;
    }

    /**
     * 加精或取消加精
     * @param discussPostId
     * @return
     */
    public int doQuintessence(Integer discussPostId){
        DiscussPost post = discussPostMapper.selectDiscussPostById(discussPostId);
        if (post == null){
            return HANDLE_FAIL;
        }
        Integer status = post.getStatus();
        if (status.equals(DiscussPostStatusEnum.BLACKLIST.getCode())){
            return HANDLE_FAIL;
        }
        int rows= status.equals(DiscussPostStatusEnum.NORMAL.getCode())?
            discussPostMapper.updateDiscussPostsStatus(discussPostId,DiscussPostStatusEnum.QUINTESSENCE.getCode()):
            discussPostMapper.updateDiscussPostsStatus(discussPostId,DiscussPostStatusEnum.NORMAL.getCode());
        redisTemplate.opsForSet().add(RedisKeyUtil.getVariationPostKey(),discussPostId);
        return rows;
    }

    /**
     * 删除帖子
     * @param discussPostId
     * @param user
     * @return
     */
    public int delete(Integer discussPostId,User user){
        DiscussPost post = discussPostMapper.selectDiscussPostById(discussPostId);
        if (post == null || !(user.getType().equals(AUTHORITY_ADMIN_CODE) || user.getId().equals(post.getUserId()))){
            return HANDLE_FAIL;
        }
        Event event=getDeletePostEvent(post.getId(),user.getId());
        int row = discussPostMapper.updateDiscussPostsStatus(discussPostId, DiscussPostStatusEnum.BLACKLIST.getCode());
        eventProducer.fireEvent(event);
        return row;
    }

    public DiscussPost getDraft(Integer userId){
        return discussPostMapper.selectDraftPost(userId);
    }

    private Event getDeletePostEvent(Integer postId,Integer userId) {
        Event event = new Event();
        event.setTopic(TOPIC_REMOVED);
        event.setSourceId(userId);
        event.setEntityType(ENTITY_TYPE_POST);
        event.setEntityId(postId);
        return event;
    }

    public int updatePostScore(Integer postId,Double score){
        return discussPostMapper.updateDiscussPostScore(postId,score);
    }

    /**
     * 保存草稿
     * @param discussPost
     * @return
     */
    public int addDraft(DiscussPost discussPost) {
        discussPost.setDraft(DISCUSSPOST_DRAFT);
        discussPost.setTitle(sensitiveFilter.filter(HtmlUtils.htmlEscape(discussPost.getTitle())));
        discussPost.setContent(sensitiveFilter.filter(HtmlUtils.htmlEscape(discussPost.getContent())));
        int rows;
        if (discussPost.getId()==null){
            discussPost.setCreateTime(new Date());
            rows = discussPostMapper.insertDiscussPost(discussPost);
        }else {
            rows = discussPostMapper.updateDraft(discussPost);
            if (elasticsearchService.existsDiscussPostById(discussPost.getId())){
                Event event = getDeletePostEvent(discussPost.getId(), discussPost.getUserId());
                eventProducer.fireEvent(event);
            }
        }
        return rows;
    }
}
